#include "arch/loongarch.h"
#include "arch/ls7a.h"
#include "config/type.h"
#include "drivers/kbd.h"
#include "lib/stdout.h"
#include "mm/phylayout.h"
#include "sched/proc.h"
#include "sched/spinlock.h"

#define UNKNOWN_INTR	-1
#define HWI_INTR		 0
#define TIMER_INTR		 1

struct spinlock tickslock;
uint ticks = 0;

// ASM
extern void kernelvec(void);			// kernel_trap.S
extern void uservec(void);				// user_trap.S
// void userret(TRAPFRAME, pagetable)
extern void handle_merror(void);		// handle_merror.S
extern void handle_tlb_refill(void);	// handle_tlb_refill.S

// C
extern void ls7a_intc_init(void);
extern void ls7a_intc_complete(uint64 irq);
extern void extioi_init(void);
extern void extioi_complete(uint64 irq);
extern uint64 extioi_claim(void);
extern void extioi_complete(uint64 irq);
extern void i8042_init(void);
extern void syscall(void);

void timer_interrupt(void) {
	// 定期清空键盘缓冲区
	kbd_clear_buffer();

	acquire(&tickslock);
	ticks++;
	// wakeup(&ticks);
	release(&tickslock);

	// 时钟中断 ack
	w_csr_ticlr(r_csr_ticlr() | CSR_TICLR_CLR);
}

void uart0_interrupt(void) { 
	pr_debug(uart0, "interrupt"); 
}

void keyboard_interrupt(void) { 
	keyboard_handler(); 
}

void mouse_interrupt(void) { 
	pr_debug(mouse, "interrupt"); 
}

void machine_trap() {
	panic(machine_trap, "machine error"); 
}

int device_interrupt() {
	uint32 estat = r_csr_estat();
	uint32 ecfg = r_csr_ecfg();

	if (estat & ecfg & HWI_VEC) {
		// 硬件中断处理
		uint64 irq = extioi_claim();
		if (irq & (1UL << UART0_IRQ)) {
			uart0_interrupt();
		}
		if (irq & (1UL << KEYBOARD_IRQ)) {
			keyboard_interrupt();
		}
		if (irq & (1UL << MOUSE_IRQ)) {
			mouse_interrupt();
		}
		// 需要保证此处 ack 正确发送
		ls7a_intc_complete(irq);
		extioi_complete(irq);
		// 要求编译器和处理器不要在这一点上移动加载或存储，保证前面的任务完成
		__sync_synchronize();
		return HWI_INTR;
	} else if (estat & ecfg & TI_VEC) {
		// 时钟中断处理
		timer_interrupt();
		return TIMER_INTR;
	} else {
		// NOTE: 如果遇到出现 estat & ecfg = 0
		// 首先检查上面的中断处理函数是不是挂了导致 ls7a 或 extioi 没有正确 ack
		panic(kernel_trap_handler, 
		"unexpected interrupt: estat = %x, ecfg = %x, era=%p, badi=%x", 
		estat, ecfg, r_csr_era(), r_csr_badi());
		return UNKNOWN_INTR;
	}
}

void kernel_trap_handler(void) {
	int res = 0;
	uint64 era = r_csr_era();
	uint64 prmd = r_csr_prmd();

	// NOTE: 内核中断前后的中断使能位由硬件自身的 CSR.PRMD 控制
	// ertn 时就会发生 CRMD.IE = PRMD.PIE 恢复以前的中断使能位 
	// 所以不要在这个函数的开头结尾手动调用 intr_on(), intr_off()

	if ((prmd & PRMD_PPLV) != 0)
		pr_error(kernel_trap_handler, "not from privilege0");
	if (intr_get() != 0)
		pr_error(kernel_trap_handler, "interrupts enabled");

	res = device_interrupt();
	// pr_warn(kernel_trap_handler, "res = %d (device_interrupt())", res);

	// 当设备中断是时间中断 + 当前进程运行时，进行调度
	// if (res == TIMER_INTR && myproc() != 0 && myproc()->state == RUNNING)
	// 	pr_warn(kernel_trap_handler, "yield()");
    // 	yield();

	// 恢复 CSR.ERA 和 PRMD
	w_csr_era(era);
	w_csr_prmd(prmd);
	// pr_warn(kernel_trap_handler, "res = %d, restore era = %x, prmd = %x", res, era, prmd);
}

// 处理来自用户空间的中断、异常或系统调用。
// 由 user_trap.S 的 uservec() 调用本函数。
void usertrap(void) {
	int res = 0;

	if ((r_csr_prmd() & PRMD_PPLV) == 0)
		panic(user_trap_handler, "not from user mode");

	// 设置中断和异常入口配置（因为现在在内核态）
	w_csr_eentry((uint64)kernelvec);

	struct proc *p = myproc();
	// 当前进程保存用户程序计数器。
	p->trapframe->era = r_csr_era();

	if (((r_csr_estat() & CSR_ESTAT_ECODE) >> 16) == 0xb) {
		// 说明中断为 system call
		pr_warn(usertrap, "syscall");
		if (p->killed)
			exit(-1);
		// sepc 指向 ecall 指令，但我们想返回到下一条指令。
		p->trapframe->era += 4;
		// 中断会改变 crmd 和 prmd 寄存器，所以在处理完这些寄存器之后才能启用。
		intr_on();
		// 进入 syscall 入口
		syscall();
	} else if ((res = device_interrupt()) == HWI_INTR) {
		// 说明中断为设备中断
	} else {
		// NOTE: 和内核中断一样，首先检查前面的函数是不是挂了
		panic(user_trap_handler, "unexpected interrupt: estat = %x, pid = %d, era=%p, badi=%x", 
			r_csr_estat(), p->pid, r_csr_era(), r_csr_badi());
		p->killed = 1;
	}

	if (p->killed)
		exit(-1);

	// 如果是时钟中断，就放弃CPU。
	if (res == TIMER_INTR) {
		pr_warn(kernel_trap_handler, "yield()");
		yield();
	}

	usertrapret();
}

// 返回用户空间
void usertrapret(void) {
	struct proc *p = myproc();

	// 我们要把中断目的地从 kerneltrap() 切换到 usertrap()，
	// 所以要关闭中断，直到我们回到用户空间才能正确的使用 usertrap()。
	intr_off();

	// 系统调用、中断和异常都发送到 user_trap.S 的 uservec()
	w_csr_eentry((uint64)uservec); 

	//设置 trapframe，当进程下次重新进入内核时，uservec() 会需要这些值。
	p->trapframe->kernel_pgdl = r_csr_pgdl();    	// 内核页页表
	p->trapframe->kernel_sp = p->kstack + PGSIZE;	// 进程的内核栈
	p->trapframe->kernel_trap = (uint64)usertrap;	// 换成用户中断入口
	p->trapframe->kernel_hartid = r_tp(); 			// cpu 的 hartid 

	// 设置寄存器使 user_trap.S 的 ertn 能进入用户空间。

	// 改变 PRMD 属性为用户态，下次 ertn 要用
	uint32 x = r_csr_prmd();
	x |= PRMD_PPLV;		// 设置 PRMD.PPLV = 3 
	x |= PRMD_PIE; 		// 设置 PRMD.PIE = 1
	w_csr_prmd(x);

	// 将用户程序计数器设置为保存的用户 PC。
	w_csr_era(p->trapframe->era);

	// 为 user_trap.S 传递用户页页表
	volatile uint64 pgdl = (uint64)(p->pagetable);

	// 跳转到 user_trap.S 
	// 由汇编函数完成切换到用户页页表，恢复用户寄存器，用 ertn 返回用户态的工作
	userret(TRAPFRAME, pgdl);
}

void trap_init(void) {
	// 例外配置寄存器设置
	uint32 ecfg = (0U << CSR_ECFG_VS_SHIFT) | HWI_VEC | TI_VEC;
	// 时钟配置寄存器设置
	uint64 tcfg = 0x0a000000UL | CSR_TCFG_EN | CSR_TCFG_PER;
	w_csr_ecfg(ecfg);
	w_csr_tcfg(tcfg);

	// 初始化时钟中断次数锁
	initlock(&tickslock, "tilock");

	// 配置内核中断入口、TLB重填入口、机器错误入口
	w_csr_eentry((uint64)kernelvec);
	w_csr_tlbrentry((uint64)handle_tlb_refill);
	w_csr_merrentry((uint64)handle_merror);

	pr_warn(trap_init, "after set csr_entry: crmd = %x, prmd = %x", r_csr_crmd(), r_csr_prmd());
	// 拓展 io 中断初始化
	extioi_init();
	// 桥片初始化
	ls7a_intc_init();
	// 键鼠控制芯片初始化
	i8042_init();
	pr_warn(trap_init, "after i8042_init: crmd = %x, prmd = %x", r_csr_crmd(), r_csr_prmd());
	intr_on();
	pr_warn(trap_init, "after intr_on: crmd = %x, prmd = %x", r_csr_crmd(), r_csr_prmd());
	pr_warn(trap_init, "kernel trap setting is completed");
}
